🧬 C 语言 · 面向对象改造
⚠️ 警告:本文已被标记为「恶搞 13.9 · C 的 OOP 邪典」
以下内容将展示如何在 “纯 C 语言” 里实现面向对象——
不使用 Objective-C,也不在 C++ 里面写 C。
我们是 “基于类的底层实现来硬搞”
如果你真的在生产环境使用这些技术,你的 C 代码会 “既像 C 又像 C++ 但两者都不是”
然后你的同事会 “彻底精神分裂”
📦
封装
struct + 函数指针
🧬
继承
struct 嵌套
🎭
多态
vtable + 函数指针
🔒
抽象
不完整的 struct

如何让你的 C 语言面向对象

不使用 Objective-C 或者在 C++ 里面写 C,我们是真的基于类的底层实现来给你搞一个
📅 2026 年 8 月 18 日 ⏱ 阅读耗时:约 10 分钟 # 整活 · # C · # OOP · # 底层实现

有人说:“C 语言是 ‘面向过程’ 的,不适合面向对象。”
有人说:“如果你想在 C 里做 OOP,就用 Objective-C。”
还有人说:“那就直接在 C++ 里面写 C 代码呗。”

但这些都是 “逃避”。真正的 C 程序员 “不逃避”。 我们要在 “纯 C” 里实现面向对象——不是“模拟”,不是“伪装”, 而是 “基于类的底层实现”。我们要用 struct函数指针void*malloc“硬造”封装、继承、多态、抽象 这四大支柱。

今天,我们就来学习这门 “C 的 OOP 邪典”。准备好了吗?让我们 “用 C 写 C++”——但比 C++ 更 “硬核”

🧬 OOP 邪典宣言 — “C++ 说:‘我有 ‘类’。’
C 说:‘我也有,我用 ‘struct’。’
C++ 说:‘我有 ‘继承’。’
C 说:‘我也有,我用 ‘struct 嵌套’。’
C++ 说:‘我有 ‘多态’。’
C 说:‘我也有,我用 ‘函数指针’。’
这不是模仿,这是 ‘底层超越’。”

一、封装:用 struct 当“类”,用函数指针当“方法”

1.1 C 的“类”长什么样

在 C++ 里,类有 “成员变量”“成员函数”。 在 C 里,我们用 struct“成员变量”,用 “函数指针”“成员函数”。这叫 “手动绑定”

            // 用 struct 定义一个“类”
            typedef struct {
                // 成员变量
               int x;
               int y;

                // 成员函数(函数指针)
               void (*setX)(void*, int);
               int  (*getX)(void*);
               void (*print)(void*);
            } Point;

            // 函数实现(第一个参数是 this 指针)
            void point_setX(void* self, int v) {
                ((Point*)self)->x = v;
            }
            int point_getX(void* self) {
                return ((Point*)self)->x;
            }
            void point_print(void* self) {
                Point* p = (Point*)self;
                printf("(%d, %d)\n", p->x, p->y);
            }

            // “构造函数”
            Point* new_Point(int x, int y) {
                Point* p = (Point*)>malloc(sizeof(Point));
                p->x = x;
                p->y = y;
                p->setX = point_setX;
                p->getX = point_getX;
                p->print = point_print;
                return p;
            }
            // 这就是 C 的“类”——没有语法糖,全是“手工活”
        
🧠 封装哲学 — “C++ 的封装是 ‘编译器帮忙’ 的。
C 的封装是 ‘自己动手’ 的。
‘手动’ 把函数指针塞进 struct,
‘手动’ 传递 this 指针,
这是一种 ‘硬核的浪漫’。”

二、继承:用 struct 嵌套 模拟“父类”

2.1 把父类“放”在子类里

C++ 的继承是“is-a”关系。在 C 里,我们用一个 struct “包含” 另一个 struct——让“父类”成为“子类”的 “第一个成员”。 这样,子类的指针就可以 “直接” 当父类的指针用。

            // “父类”
            typedef struct {
                int x;
                int y;
                void (*print)(void*);
            } Shape;

            // “子类”——把 Shape 放在第一个位置
            typedef struct {
                Shape parent;   // 继承 Shape
                int radius;     // 子类自己的成员
            } Circle;

            // 子类的“构造函数”
            Circle* new_Circle(int x, int y, int r) {
                Circle* c = (Circle*)malloc(sizeof(Circle));
                c->parent.x = x;
                c->parent.y = y;
                c->parent.print = circle_print;   // 重写父类方法
                c->radius = r;
                return c;
            }

            // 现在你可以把 Circle* 当作 Shape* 用
            Shape* s = (Shape*)c;   // 向上转型
            s->print(s);            // 调用的是 circle_print
            // 这就是 C 的“继承”——“第一个成员”继承法
        
🧠 继承哲学 — “C++ 的继承是 ‘语言内置’ 的。
C 的继承是 ‘内存布局’ 的。
你把父类放在子类的 ‘第一个’ 位置,
指针 ‘自然’ 就能向上转型。
这是一种 ‘内存级’ 的继承。”

三、多态:用 vtable 实现“动态派发”

3.1 手动实现虚表

C++ 的多态靠 “虚表”(vtable)。C 里没有,但我们可以 “手动” 造一个。用一个 “函数指针表” 来存储所有虚函数,然后每个对象 “指向” 这个表。这叫 “手动虚表”——一种 “体力活”

            // 接上文
            // 虚表(vtable)
            typedef struct {
                void (*print)(void*);
                void (*move)(void*, int, int);
            } ShapeVTable;

            // 基类:包含虚表指针
            typedef struct {
                ShapeVTable* vtable;
                int x, y;
            } Shape;

            // 子类:继承基类
            typedef struct {
                Shape parent;
                int radius;
            } Circle;

            // 虚表实例(每个类只有一个)
            ShapeVTable shape_vtable = { shape_print, shape_move };
            ShapeVTable circle_vtable = { circle_print, circle_move };

            // 现在调用时,通过 vtable 间接调用
            s->vtable->print(s);   // 多态!
            // 这就是 C 的“多态”——比 C++ 的虚表更“透明”
        
💀 多态的代价 — 手动 vtable 意味着你要 “手动维护” 每一个类的虚表。你添加一个虚函数,你要改 “所有” 类的虚表。 这叫 “手工多态”——一种 “体力密集型” 的 OOP。

四、抽象:用 不完整的 struct 实现“接口”

4.1 你不告诉我细节,我就无法实例化你

C++ 的抽象类 “不能被实例化”。在 C 里,我们可以在 .h 文件里 “声明” 一个 struct,但不 “定义” 它。这样,外部代码 只能通过 “指针” 操作它,不能直接分配它。这叫 “不透明指针” (Opaque Pointer)——一种 “手动抽象”

            // shape.h —— 抽象基类
            typedef struct Shape Shape;   // 只声明,不定义

            // 只能通过函数操作 Shape
            void shape_print(Shape* s);
            void shape_move(Shape* s, int dx, int dy);
            Shape* shape_create(int x, int y);

            // shape.c —— 具体定义(用户看不到)
            struct Shape {
                int x, y;
                void (*print)(Shape*);
            };
            // 用户不知道 Shape 的内部结构——只能通过接口操作
            // 这就是 C 的“抽象”——一种“信息隐藏”的暴力美学
        
🧠 抽象哲学 — “C++ 的抽象是 ‘语法级’ 的。
C 的抽象是 ‘编译单元级’ 的。
你在头文件里藏起 struct 的定义,
用户只能看到 ‘指针’,看不到 ‘内容’
这是一种 ‘物理级’ 的封装。”

五、完整示例:C 的 OOP 代码

            // ============ shape.h ============
            #ifndef SHAPE_H
            #define SHAPE_H

            typedef struct Shape Shape;

            Shape* shape_new(int x, int y);
            void shape_delete(Shape* s);
            void shape_print(Shape* s);
            void shape_move(Shape* s, int dx, int dy);

            #endif

            // ============ shape.c ============
            #include "shape.h"
            #include <stdio.h>
            #include <stdlib.h>

            struct Shape {
                int x, y;
                void (*print)(Shape*);
            };

            static void shape_print_impl(Shape* s) {
                printf("Shape at (%d, %d)\n", s->x, s->y);
            }

            Shape* shape_new(int x, int y) {
                Shape* s = malloc(sizeof(Shape));
                s->x = x; s->y = y;
                s->print = shape_print_impl;
                return s;
            }

            void shape_print(Shape* s) { s->print(s); }
            void shape_move(Shape* s, int dx, int dy) { s->x += dx; s->y += dy; }
            void shape_delete(Shape* s) { free(s); }

            // ============ main.c ============
            #include "shape.h"

            int main() {
                Shape* s = shape_new(10, 20);
                shape_print(s);
                shape_move(s, 5, -3);
                shape_print(s);
                shape_delete(s);
                return 0;
            }
            // 这就是 C 的 OOP——没有语法糖,全是“手工活”
            // 但它“确实”是面向对象的
        

六、C OOP 的“段位”体系


😌 写在最后(真诚版)

以上所有内容都是 “夸张” 的,但它反映了 “一个真实的现象”

如果你真的想在 C 里用 OOP,“请考虑一下你的维护成本”
手动虚表、手动继承、手动封装——这些都 “可以工作”,但 “不值得”

如果你需要 OOP,“请用 C++”。它不是“花哨”,它是 “工具”
如果你坚持用 C,“请接受它的过程式本质”——它不是“落后”,它是 “简洁”

—— 一个曾经在 C 里实现 OOP,然后发现 C++ 更省事的人